[iOS] 「LINE」アプリのような写真選択画面を作るならAssetsLibraryを使おう!
iPhone内の画像を取得する
こんにちは!クラスメソッド白猫部副部長の荒川です。
iPhoneのカメラロールに保存された画像を取り出す時、 UIImagePickerController が大変便利です。
しかしながら、「LINE」アプリのように画像選択の一覧画面をカスタマイズしたり、複数の画像を選択したりする機能が必要となれば、 UIImagePickerController では実現出来ません。
今回はそれを実現するための AssetsLibrary について紹介致します。
ちなみに、 アプリの対応バージョンが iOS 8 以降 であれば、弊社平屋がシリーズ化している PhotoKit を使用することをオススメします。とはいえまだまだ iOS 7 対応は切り捨てられませんので、AssetsLibraryを使うことになるでしょう。
AssetsLibrary を使う
AssetsLibrary を使用することによって、iPhone 内に保存されているアセットを全て ALAsset オブジェクトとして取得することが可能です。
ALAsset には画像以外にも Exif などのメタ情報や、サムネイル、保存場所の URL などが含まれています。また、画像だけでなく動画も取得することができます。iOS のリスト形式のビューにもパフォーマンス良く表示できるように考えて作られているモデルオブジェクトです。
保存されている画像を取得する
AssetsLibrary は以下のように使用します。
#import <AssetsLibrary/AssetsLibrary.h> @interface MenuViewController () @property (nonatomic, strong) ALAssetsLibrary *assetsLibrary; @property (nonatomic, strong) NSMutableArray *assets; @end ... 省略 - (void)loadAssetsLibrary { ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus]; if (authorizationStatus == ALAuthorizationStatusNotDetermined || authorizationStatus == ALAuthorizationStatusAuthorized) { // 読み込みが許可されているか読み込み許可待ちの状態 self.assetsLibrary = [ALAssetsLibrary new]; self.assets = [NSMutableArray array]; [self.assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { // 各グループから全件取得します。 if (group) { // 画像のみに絞り込みます。 [group setAssetsFilter:[ALAssetsFilter allPhotos]]; [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if (result) { [self.assets addObject:result]; } else { // 全てのアセットが読み込み完了した時の処理 } }]; } } failureBlock:^(NSError *error) { NSLog(@"アセットライブラリの読み込みに失敗しました。%@", error); }]; } else { NSLog(@"ライブラリへのアクセスが拒否されています。設定アプリから許可して下さい。"); } }
loadAssetsLibrary メソッドを任意の箇所で呼び出すことによって、self.assets にリスト表示するための ALAsset の配列が追加されます。
ALAssetsLibrary のインスタンスがメモリ上から解放されると ALAsset が使用できなくなる ので、必ず AssetsLibrary もプロパティとして保持しましょう。
この処理は一覧表示をしたい画面の一つ前の画面に挟み込むと良いでしょう。一覧画面を表示する前にアセットを全件ロードしておくことで、一覧表示時の遅延が防げます。
取得した画像を一覧表示する
上記で取得した ALAsset の配列をコレクションビューなどで表示するには、以下のように実装します。
// 一行に何個のサムネイルを表示したいか static NSInteger const AssetsCollectionViewCellCountInRow = 4; // 隣り合うサムネイルとの間隔 static CGFloat const AssetsCollectionViewCellPaddingSize = 2.0f; @interface AssetsLibraryCollectionViewController () <UICollectionViewDelegateFlowLayout> @property (nonatomic, assign) BOOL isAllAssetsLoaded; @end @implementation AssetsLibraryCollectionViewController static NSString * const AssetsLibraryAssetCellIdentifier = @"AssetCell"; #pragma mark - Lifecycle methods - (void)viewDidLoad { [super viewDidLoad]; UINib *assetNib = [UINib nibWithNibName:NSStringFromClass([AssetCollectionViewCell class]) bundle:nil]; [self.collectionView registerNib:assetNib forCellWithReuseIdentifier:AssetsLibraryAssetCellIdentifier]; } - (void)viewDidLayoutSubviews { if (!self.isAllAssetsLoaded) { self.isAllAssetsLoaded = YES; // アセットの読み込み完了後に最新の画像までスクロールします。 // 最新の画像は表示領域の最下部に表示されます。 NSIndexPath *latestIndexPath = [NSIndexPath indexPathForRow:self.assets.count - 1 inSection:0]; if (self.assets.count > 0) { [self.collectionView scrollToItemAtIndexPath:latestIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:NO]; } } } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - UICollectionViewDataSourceDelegate methods - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.assets.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { AssetCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:AssetsLibraryAssetCellIdentifier forIndexPath:indexPath]; ALAsset *asset = self.assets[indexPath.item]; cell.thumbnail.image = [UIImage imageWithCGImage:asset.thumbnail]; return cell; } #pragma mark - UICollectionViewDelegateFlowLayout method - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat cellSize = (self.collectionView.frame.size.width - AssetsCollectionViewCellPaddingSize * AssetsCollectionViewCellCountInRow) / AssetsCollectionViewCellCountInRow; return CGSizeMake(cellSize, cellSize); } @end
viewDidLayoutSubviews で最新の画像が画面最下部に表示されるように調整しています。(LINEアプリがそうなっていたため。)
AssetCollectionViewCell は xib で作成した UICollectionViewCell です。サムネイルを表示するための UIImageView である thumbnail を IBOutlet 接続しています。
ALAsset が持つ thumbnail メソッドを使用してセルに表示するサムネイルサイズのイメージを取得しています。
取得した画像の元画像を表示する
ALAsset から 元サイズの UIImage を取得するには以下のように実装します。
ALAssetRepresentation *representation = [self.selectedAsset defaultRepresentation]; // アセットライブラリから復元した画像は Orientation が予期しないものになっていることがあるので、明示的に指定します。 UIImage *image = [UIImage imageWithCGImage:[representation fullScreenImage] scale:[representation scale] orientation:(UIImageOrientation)ALAssetOrientationUp];
取得したイメージを詳細画面などで表示すると良いでしょう。
まとめ
AssetsLibrary は UIImagePickerController に比べて使用するための手順が面倒だと思っていましたが、実装してみたらそんなことはありませんでした。
画像をアップロードできるアプリはとても多いですが、UIImagePickerController に比べて情報が少なく感じたので記事にまとめました。
今回作成したアプリのサンプルコードは GitHub にアップロードしていますので、画面遷移などと合わせて確認したい方に参考になれば幸いです。